// +FHDR------------------------------------------------------------
//                 Copyright (c) 2022 .
//                       ALL RIGHTS RESERVED
// -----------------------------------------------------------------
// Filename      : cpu_driver.sv
// Author        : 
// Created On    : 2022-08-25 15:07
// Last Modified : 
// -----------------------------------------------------------------
// Description:
//
//
// -FHDR------------------------------------------------------------

`ifndef __CPU_DRIVER_SV__
`define __CPU_DRIVER_SV__

class cpu_driver extends uvm_driver #(cpu_transaction);

	virtual cpu_interface vif;
    `ifdef UTILS_TEST
    uvm_analysis_port #(cpu_transaction) ap;
    `endif
	`uvm_component_utils(cpu_driver)

	extern function new(string name = "cpu_driver", uvm_component parent=null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task run_phase(uvm_phase phase);
	extern virtual task drive_pkt(cpu_transaction pkt);
	extern virtual task drive_read_pkt(cpu_transaction pkt);
	extern virtual task drive_write_pkt(cpu_transaction pkt);
	extern virtual task drive_idle();

endclass: cpu_driver

function cpu_driver::new(string name = "cpu_driver", uvm_component parent=null);
	super.new(name, parent);
endfunction: new

function void cpu_driver::build_phase(uvm_phase phase);
	super.build_phase(phase);
	`uvm_info("cpu_driver", "build_phase is called", UVM_HIGH);
	if(!uvm_config_db #(virtual cpu_interface)::get(this, "", "vif", vif))begin
		`uvm_fatal("cpu_driver", "virtual interface get fatal");
	end
    `ifdef UTILS_TEST
    ap   = new("ap", this);
    `endif
endfunction: build_phase

task cpu_driver::run_phase(uvm_phase phase);
    super.run_phase(phase);
	//phase.raise_objection(this);
	`uvm_info("cpu_driver", "run_phase is called", UVM_HIGH);
    
    this.drive_idle();
    while(!this.vif.rst_n) @this.vif.drv;

    @this.vif.drv;
	while(1)begin
        cpu_transaction req;
		seq_item_port.try_next_item(req);
		if(req == null) begin//break;
            this.drive_idle();
            continue;
        end
		else begin
	        `uvm_info("cpu_driver", "drive a pkt start", UVM_HIGH);
            `ifdef UTILS_TEST
            ap.write(req.clone());
            $display("drv send req, addr = 'h%0h", req.CPU_ADDR);
            `endif
			this.drive_pkt(req);
			seq_item_port.item_done();
		end
	end
    this.drive_idle();

    //#1000;
	//phase.drop_objection(this);
endtask: run_phase

task cpu_driver::drive_pkt(cpu_transaction pkt);
    case(pkt.CPU_RW_TYPE)
        1'b0: drive_read_pkt(pkt);
        1'b1: drive_write_pkt(pkt);
    endcase
    repeat(pkt.cycle_post) this.drive_idle();
endtask: drive_pkt

task cpu_driver::drive_read_pkt(cpu_transaction pkt);
	pkt.pack();

    this.vif.drv.CPU_CS_N <= 1'b0;
    repeat($urandom_range(1,10)) @this.vif.drv;//Tcas > 10ns
    this.vif.drv.CPU_RD_N <= 1'b0;
    this.vif.drv.CPU_ADDR <= pkt.CPU_ADDR;
    repeat($urandom_range(8,8)) @this.vif.drv;//Tcdrd: 80~200
    while(1)begin 
        if(this.vif.drv.CPU_RDY_N === 1'd0) break;
        @this.vif.drv;
    end
    this.vif.drv.CPU_RD_N <= 1'b1;
    pkt.CPU_DATA = this.vif.drv.CPU_RDATA;
    repeat($urandom_range(1,10)) @this.vif.drv;//Tcas > 10ns
    this.vif.drv.CPU_CS_N <= 1'b1;
    //`uvm_info("cpu_driver", "dddd", UVM_LOW);
    repeat($urandom_range(20, 30)) @this.vif.drv;
    
endtask: drive_read_pkt

task cpu_driver::drive_write_pkt(cpu_transaction pkt);
	pkt.pack();

    this.vif.drv.CPU_CS_N <= 1'b0;
    repeat($urandom_range(1,10)) @this.vif.drv;//Tcas > 10ns
    this.vif.drv.CPU_WE_N  <= 1'b0;
    this.vif.drv.CPU_WDATA <= pkt.CPU_DATA;
    this.vif.drv.CPU_ADDR  <= pkt.CPU_ADDR;
    repeat($urandom_range(8,8)) @this.vif.drv;//Tcdrd: 80~200
    while(1)begin 
        if(this.vif.drv.CPU_RDY_N === 1'd0) break;
        @this.vif.drv;
    end
    this.vif.drv.CPU_WE_N <= 1'b1;
    this.vif.drv.CPU_WDATA <= pkt.CPU_DATA;
    repeat($urandom_range(1,10)) @this.vif.drv;//Tcas > 10ns
    this.vif.drv.CPU_CS_N <= 1'b1;
    repeat($urandom_range(20, 30)) @this.vif.drv;
    
endtask: drive_write_pkt

task cpu_driver::drive_idle();
    @this.vif.drv;
    this.vif.drv.CPU_CS_N <= 'd1;
    this.vif.drv.CPU_RD_N <= 'd1;
    this.vif.drv.CPU_WE_N <= 'd1;
    this.vif.drv.CPU_ADDR <= 'dx;
    this.vif.drv.CPU_WDATA <= 'dx;
endtask: drive_idle

`endif
